/****************************************************************************\
*                                                                            *
*  Copyright 2008 The CAnt2 Project (http://code.google.com/p/cant2)         *
*                                                                            *
*  Licensed under the Apache License, Version 2.0 (the "License");           *
*  you may not use this file except in compliance with the License.          *
*  You may obtain a copy of the License at                                   *
*                                                                            *
*      http://www.apache.org/licenses/LICENSE-2.0                            *
*                                                                            *
*  Unless required by applicable law or agreed to in writing, software       *
*  distributed under the License is distributed on an "AS IS" BASIS,         *
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
*  See the License for the specific language governing permissions and       *
*  limitations under the License.                                            *
*                                                                            *
\****************************************************************************/

#include "xml.h"
#include "XmlCharacterClassification.h"

#include <istream>
#include <stdexcept>
#include <vector>
#include <utility>
#include <string>

#include <iostream>

/****************************************************************************/

namespace {

    #include "CircularQueue.h"

    struct TrackedCharacter
    {
        unsigned    line;
        unsigned    col;
        int         ucs8;
    };

    void swap ( TrackedCharacter& lhs, TrackedCharacter& rhs )
    {
        std::swap ( lhs.line    , rhs.line  );
        std::swap ( lhs.col     , rhs.col   );
        std::swap ( lhs.ucs8    , rhs.ucs8  );
    }

    wchar_t Ucs8ToChar ( int ucs8 )
    {
        return wchar_t ( ucs8 );
    }

    class CharacterDecoder
    {
    public:
        CharacterDecoder ( std::istream& is ) : is ( is ), line(1), col(1) {}

        void Next ( TrackedCharacter& out )
        {
            if ( queue.Used() < LowWaterMark ) Fill();
            queue.Dequeue ( out );
        }

        void Eat ( unsigned n )
        {
            while ( n-- )
            {
                TrackedCharacter unused;
                Next ( unused );
            }
        }

        const TrackedCharacter& Peek ( unsigned n )
        {
            if ( n > LowWaterMark ) throw std::runtime_error ( "Too empty" );
            if ( queue.Used() < LowWaterMark ) Fill();
            return queue.Peek ( n );
        }

    private:
        void Fill()
        {
            char buf[BufferSize];
            unsigned n = queue.Capacity() - queue.Used();
            is.read ( buf, n );
            unsigned r = is.gcount();
            unsigned i = 0;
            while ( i < r )
            {
                TrackedCharacter tc;
                tc.line = line;
                tc.col  = col;
                tc.ucs8 = buf[i];
                queue.Enqueue ( tc );

                if ( buf[i] == '\r' )
                {
                    line += 1;
                    col  = 1;
                } else
                if ( buf[i] != '\n' )
                {
                    col += 1;
                }

                i += 1;
            }

            while ( i < n )
            {
                TrackedCharacter tc;
                tc.line = line;
                tc.col  = col;
                tc.ucs8 = -1;
                queue.Enqueue ( tc );

                i += 1;
            }
        }

    private:
        const static int BufferSize    = 16;
        const static int LowWaterMark  = 8;
        std::istream& is;
        int line;
        int col;
        CircularQueue < TrackedCharacter, BufferSize > queue;
    };
}

/****************************************************************************/

IXmlParseEvent::~IXmlParseEvent()
{
}

/****************************************************************************/

class XmlParser::Details
{
public:
    explicit Details ( std::istream& is, IXmlParseEvent& report ) : characterDecoder ( is ), report ( report ), state ( CData )
    {
    }

    void Run ()
    {
        for (;;)
        {
            TrackedCharacter tc;
            characterDecoder.Next ( tc );

            if ( tc.ucs8 == -1 )
            {
                report.EndOfFile();
                break;
            }

#define HANDLE(S) case S : Process##S ( tc ); break

            switch ( state )
            {
                default : throw std::runtime_error ( "Missing FSM state!");
                HANDLE ( CData          );
                HANDLE ( Comment        );
                HANDLE ( OEBegin        );
                HANDLE ( OEName         );
                HANDLE ( OEAttrOrEnd    );
                HANDLE ( OEAttrName     );
                HANDLE ( OEAttrPostName );
                HANDLE ( OEAttrPostEq   );
                HANDLE ( OEAttrValSQ    );
                HANDLE ( OEAttrValDQ    );
                HANDLE ( CEBegin        );
                HANDLE ( CEName         );
                HANDLE ( CEEnd          );
            }
        }
    }

private:

#define PROCESS(S) void Process##S ( const TrackedCharacter& curr )
    PROCESS ( CData         )
    {
        if ( curr.ucs8 == '<' )
        {
            const TrackedCharacter& next0 = characterDecoder.Peek(0);
            const TrackedCharacter& next1 = characterDecoder.Peek(1);
            const TrackedCharacter& next2 = characterDecoder.Peek(2);

            if ( next0.ucs8 == '!' )
            {
                if ( ( next1.ucs8 == '-') && ( next2.ucs8 == '-' ) )
                {
                    state = Comment;
                    characterDecoder.Eat ( 3 );
                    return;
                }
                std::cout << "Encountered '<!'...\n";
                throw std::runtime_error ( "XML parsing limited. Can't read this.");
            } else
            if ( next0.ucs8 == '?' )
            {
                std::cout << "Encountered '<?'...\n";
                throw std::runtime_error ( "XML parsing limited. Can't read this.");
            } else
            if ( next0.ucs8 == '/' )
            {
                state = CEBegin;
                characterDecoder.Eat ( 1 );
                return;
            }

            state = OEBegin;
            return;
        }

        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) )
        {
            return;
        }

        std::cout << "Encountered '" << char ( curr.ucs8 ) << "'\n";
        throw std::runtime_error ( "XML parsing limited. Can't read this.");
    }

    PROCESS ( Comment           )
    {
        if ( curr.ucs8 == '-' )
        {
            const TrackedCharacter& next0 = characterDecoder.Peek(0);
            const TrackedCharacter& next1 = characterDecoder.Peek(1);

            if ( ( next0.ucs8 == '-') && ( next1.ucs8 == '>' ) )
            {
                state = CData;
                characterDecoder.Eat ( 2 );
                return;
            }
        }
    }

    PROCESS ( OEBegin           )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) ) return;
        if ( !Xml1p1::IsNameStart ( curr.ucs8 ) )
        {
            throw std::runtime_error ( "Not a valid name start");
        }

        ename.clear();
        ename.push_back ( Ucs8ToChar ( curr.ucs8 ) );
        state = OEName;
    }

    PROCESS ( OEName            )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) )
        {
            report.ElementBegin ( ename.c_str() );
            state = OEAttrOrEnd;
            return;
        }

        if ( curr.ucs8 == '/' )
        {
            const TrackedCharacter& next0 = characterDecoder.Peek(0);
            if ( next0.ucs8 == '>' )
            {
                report.ElementBegin ( ename.c_str() );
                report.ElementEnd   ( ename.c_str() );
                state = CData;
                characterDecoder.Eat ( 1 );
                return;
            }
        }

        if ( curr.ucs8 == '>' )
        {
            report.ElementBegin ( ename.c_str() );
            state = CData;
            return;
        }

        if ( !Xml1p1::IsNameContinue ( curr.ucs8 ) )
        {
            throw std::runtime_error ( "Not a valid name continue");
        }

        ename.push_back ( Ucs8ToChar ( curr.ucs8  ) );
    }

    PROCESS ( OEAttrOrEnd       )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) ) return;

        if ( curr.ucs8 == '/' )
        {
            const TrackedCharacter& next0 = characterDecoder.Peek(0);
            if ( next0.ucs8 == '>' )
            {
                report.ElementEndAttributes ( ename.c_str() );
                report.ElementEnd   ( ename.c_str() );
                state = CData;
                characterDecoder.Eat ( 1 );
                return;
            }
        }

        if ( curr.ucs8 == '>' )
        {
            report.ElementEndAttributes ( ename.c_str() );
            state = CData;
            return;
        }

        if ( !Xml1p1::IsNameStart ( curr.ucs8 ) )
        {
            throw std::runtime_error ( "Not a valid name start");
        }

        aname.clear();
        aname.push_back ( Ucs8ToChar ( curr.ucs8 ) );
        state = OEAttrName;
    }

    PROCESS ( OEAttrName        )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) )
        {
            state = OEAttrPostName;
            return;
        }
        if ( curr.ucs8 == '=' )
        {
            state = OEAttrPostEq;
            return;
        }
        if ( !Xml1p1::IsNameContinue ( curr.ucs8 ) )
        {
            throw std::runtime_error ( "Not a valid name continue");
        }

        aname.push_back ( Ucs8ToChar ( curr.ucs8 ) );
    }

    PROCESS ( OEAttrPostName    )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) ) return;

        if ( curr.ucs8 == '=' )
        {
            state = OEAttrPostEq;
            return;
        }

        throw std::runtime_error ( "Unexpected");
    }

    PROCESS ( OEAttrPostEq      )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) ) return;

        if ( curr.ucs8 == '\'' )
        {
            avalue.clear();
            state = OEAttrValSQ;
            return;
        }
        if ( curr.ucs8 == '\"' )
        {
            avalue.clear();
            state = OEAttrValDQ;
            return;
        }
    }

    void OEAttrVal ( const TrackedCharacter& curr, int endChar )
    {
        if ( curr.ucs8 == endChar )
        {
            report.Attribute ( aname.c_str(), avalue.c_str() );
            state = OEAttrOrEnd;
            return;
        }

        avalue.push_back ( Ucs8ToChar ( curr.ucs8 ) );
    }

    PROCESS ( OEAttrValSQ       )
    {
        OEAttrVal ( curr, '\'' );
    }

    PROCESS ( OEAttrValDQ       )
    {
        OEAttrVal ( curr, '\"' );
    }

    PROCESS ( CEBegin           )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) ) return;
        if ( !Xml1p1::IsNameStart ( curr.ucs8 ) )
        {
            throw std::runtime_error ( "Not a valid name start");
        }

        ename.clear();
        ename.push_back ( Ucs8ToChar ( curr.ucs8 ) );
        state = CEName;
    }

    PROCESS ( CEName            )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) )
        {
            state = CEEnd;
            return;
        }

        if ( curr.ucs8 == '>' )
        {
            report.ElementEnd ( ename.c_str() );
            state = CData;
            return;
        }

        if ( !Xml1p1::IsNameContinue ( curr.ucs8 ) )
        {
            throw std::runtime_error ( "Not a valid name continue");
        }

        ename.push_back ( Ucs8ToChar ( curr.ucs8 ) );
    }

    PROCESS ( CEEnd             )
    {
        if ( Xml1p1::IsWhitespace ( curr.ucs8 ) ) return;

        if ( curr.ucs8 == '>' )
        {
            report.ElementEnd ( ename.c_str() );
            state = CData;
            return;
        }

        throw std::runtime_error ( "Unexpected");
    }

private:
    Details ( const Details& );
    Details& operator = ( const Details& );

private:
    enum State
    {
        CData           ,
        Comment         ,
        OEBegin         ,
        OEName          ,
        OEAttrOrEnd     ,
        OEAttrName      ,
        OEAttrPostName  ,
        OEAttrPostEq    ,
        OEAttrValSQ     ,
        OEAttrValDQ     ,
        CEBegin         ,
        CEName          ,
        CEEnd           ,
    };

private:
    CharacterDecoder    characterDecoder;
    IXmlParseEvent&     report;
    State               state;
    std::wstring        ename;
    std::wstring        aname;
    std::wstring        avalue;
};

/****************************************************************************/

XmlParser::XmlParser ( std::istream& is, IXmlParseEvent& xpe ) : pimpl ( new Details ( is, xpe ) )
{
}

XmlParser::~XmlParser ()
{
}

void XmlParser::Run ()
{
    return pimpl->Run ();
}

